package com.codelab.statemachine.task;

import cn.hutool.core.util.StrUtil;
import com.codelab.statemachine.mybatis.dataobject.TaskDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Component
@Slf4j
public class TaskStateMachineBuilder {

    public final static String MACHINEID = "taskStateMachine";
    @Autowired
    private TaskProgressHolder progressHolder;
    @Autowired
    private TaskExecuteHolder executeHolder;
    @Autowired
    private CommonTaskService taskService;

    @Autowired
    @Qualifier("taskStateMachineExecutor")
    private ThreadPoolTaskExecutor poolTaskExecutor;

    public StateMachine<TaskStates, TaskEvents> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<TaskStates, TaskEvents> builder = StateMachineBuilder.builder();
        log.info("Task Machine Create!");
        builder.configureConfiguration().withConfiguration().machineId(MACHINEID).beanFactory((beanFactory));

        builder.configureStates().withStates().
                initial(TaskStates.CREATE)
                .choice(TaskStates.EXECUTE_CHOICE)
                .state(TaskStates.READY,readyAction())
                        .states(EnumSet.allOf(TaskStates.class));

        builder.configureTransitions()
                .withExternal().source(TaskStates.CREATE).target(TaskStates.READY).event(TaskEvents.PREPARE)
                .and().withExternal().source(TaskStates.READY).target(TaskStates.EXECUTE_CHOICE).event(TaskEvents.EXECUTE).action(executeAction())
                .and().withChoice().source(TaskStates.EXECUTE_CHOICE).first(TaskStates.RUNNING,executeChoiceAction()).last(TaskStates.FAIL)
                .and().withInternal().source(TaskStates.RUNNING).action(waitRunningAction()).timer(2000)
                .and().withExternal().source(TaskStates.RUNNING).target(TaskStates.SUCCESS).event(TaskEvents.DONE)
                .and().withExternal().source(TaskStates.RUNNING).target(TaskStates.FAIL).event(TaskEvents.ERROR);

        return builder.build();
    }

    @Bean
    public Guard<TaskStates, TaskEvents> executeChoiceAction() {
        return new Guard<TaskStates, TaskEvents>() {

            @Override
            public boolean evaluate(StateContext<TaskStates, TaskEvents> context) {

                    String status = context.getStateMachine().getState().getId().toString();
                    ExecutePOJO pojo = (ExecutePOJO) context.getExtendedState().getVariables().get("exepojo");
                    log.info("Task Machine executeChoiceAction()! status[{}] exepojo[{}]",status,pojo);

                    if(pojo.getCode().equals("0")){
                        return true;
                    }else {
                        if(StrUtil.isNotBlank(pojo.getMsg())) {
                            context.getExtendedState().getVariables().put("msg", pojo.getMsg());
                        }
                        return false;
                    }
            }
        };
    }

    @Bean
    public Action<TaskStates, TaskEvents> readyAction() {
        return new Action<TaskStates, TaskEvents>() {
            @Override
            public void execute(StateContext<TaskStates, TaskEvents> stateContext) {

                String status = stateContext.getStateMachine().getState().getId().toString();
                String taskid = (String) stateContext.getExtendedState().getVariables().get("taskid");
                log.info("Task Machine readyAction()! status[{}]",status);

                CompletableFuture.runAsync(()->{
                    StateMachine<TaskStates, TaskEvents> stateMachine = stateContext.getStateMachine();
                    stateMachine.sendEvent(TaskEvents.EXECUTE);
                });

                List<TaskDO> childTasks = taskService.getChildTasks(taskid);
                for(TaskDO taskDO : childTasks) {
                    CompletableFuture.runAsync(()->{
                        taskService.startTaskMachine(taskDO.getId());
                    }, poolTaskExecutor);
                }
            }
        };
    }

    @Bean
    public Action<TaskStates, TaskEvents> executeAction() {
        return new Action<TaskStates, TaskEvents>() {
            @Override
            public void execute(StateContext<TaskStates, TaskEvents> stateContext) {
                log.info("Task Machine executeAction()! status[{}]",stateContext.getStateMachine().getState().getId().toString());
                ExecutePOJO pojo = new ExecutePOJO();
                pojo.setCode("-1");
                String taskid = (String) stateContext.getExtendedState().getVariables().get("taskid");
                TaskDO task = taskService.getTaskById(taskid);
                try {

                    TaskExecute taskExecute = executeHolder.getBean(task.getTaskCode());
                    pojo = taskExecute.execute(task.getParams());
                    if (pojo.getCode().equals("0")) {
                        log.info("================task====={}====执行完毕", task.getTaskCode());
                    }
                } catch (Exception e) {
                    log.error("================task====={}====执行失败",task.getTaskCode());
                }

                stateContext.getExtendedState().getVariables().put("exepojo",pojo);
            }
        };
    }

    @Bean
    public Action<TaskStates, TaskEvents> waitRunningAction() {

        return new Action<TaskStates, TaskEvents>() {
            @Override
            public void execute(StateContext<TaskStates, TaskEvents> stateContext) {
                log.info("Task Machine waitRunningAction()! status[{}]",stateContext.getStateMachine().getState().toString());

                String taskid = (String) stateContext.getExtendedState().getVariables().get("taskid");
                TaskDO task = taskService.getTaskById(taskid);
                StateMachine<TaskStates, TaskEvents> stateMachine = stateContext.getStateMachine();

                if(task.getStatus().equals(TaskStates.SUCCESS.toString())){
                    stateMachine.sendEvent(TaskEvents.DONE);
                    taskService.updateProcess(taskid, 1);
                    return;
                }

                if(task.getStatus().equals(TaskStates.FAIL.toString())){
                    stateMachine.sendEvent(TaskEvents.ERROR);
                    return;
                }

                TaskProgress taskProgress = progressHolder.getBean(task.getTaskCode());
                ProgressPOJO pojo = taskProgress.getProgress(task.getParams());

                List<TaskDO> childs = taskService.getChildTasks(taskid);
                ProgressPOJO childPojo = taskService.getChildTasksProgress(childs);
                double taskRate = pojo.getProgress();
                if(childs.size() > 0){
                    taskRate = pojo.getProgress() * task.getWeight();
                }
                taskRate += (1 - task.getWeight()) * childPojo.getProgress();
                boolean isDone = pojo.isDone() && childPojo.isDone();
                taskService.updateProcess(taskid, taskRate);
                if (childPojo.getCode().equals("-1")){
                    pojo.setCode("-1");
                }

                if (pojo.getCode().equals("0") && isDone) {
                    stateMachine.sendEvent(TaskEvents.DONE);
                    taskService.updateProcess(taskid, 1);
                }

                if (pojo.getCode().equals("-1")) {
                    if(StrUtil.isNotBlank(pojo.getMsg())){
                        stateMachine.getExtendedState().getVariables().put("msg", pojo.getMsg());

                    }
                    stateMachine.sendEvent(TaskEvents.ERROR);
                }
            }
        };
    }
}
